package org.budo.netty.servlet.util;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.EventListener;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.Filter;
import javax.servlet.FilterRegistration;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;

import org.budo.netty.servlet.server.handler.servlet.config.BudoFilterConfig;
import org.budo.netty.servlet.server.handler.servlet.config.BudoServletConfig;
import org.budo.netty.servlet.server.handler.servlet.registration.BudoServletRegistration;

import lombok.extern.slf4j.Slf4j;

/**
 * @author li
 */
@Slf4j
public class InstanceFactory {
    private static final Map<String, Servlet> _SERVLET_INSTANCE_CACHE = new ConcurrentHashMap<String, Servlet>();

    private static final Map<String, Filter> _FILTER_INSTANCE_CACHE = new ConcurrentHashMap<String, Filter>();

    public static Servlet servletInstance(String className, ServletContext servletContext) {
        Servlet servlet = (Servlet) _SERVLET_INSTANCE_CACHE.get(className);
        if (null != servlet) {
            return servlet;
        }

        synchronized (InstanceFactory.class) {
            initInstance(servletContext);

            servlet = (Servlet) _SERVLET_INSTANCE_CACHE.get(className);
            if (null == servlet) {
                throw new IllegalStateException("#38 servlet is null, className=" + className + ", servletRegistrations=" + servletContext.getServletRegistrations().values());
            }

            return servlet;
        }
    }

    public static Filter filterInstance(String className, ServletContext servletContext) {
        Filter filter = (Filter) _FILTER_INSTANCE_CACHE.get(className);
        if (null != filter) {
            return filter;
        }

        synchronized (InstanceFactory.class) {
            initInstance(servletContext);

            filter = (Filter) _FILTER_INSTANCE_CACHE.get(className);
            if (null == filter) {
                throw new IllegalStateException("#58 filter is null, className=" + className + ", servletContext=" + servletContext);
            }

            return filter;
        }
    }

    public static void initInstance(ServletContext servletContext) {
        initFilterInstance(servletContext); // filter在servlet前初始化
        initServletInstance(servletContext);
    }

    public synchronized static void destroyInstance() {
        for (Servlet servlet : _SERVLET_INSTANCE_CACHE.values()) {
            servlet.destroy();
        }

        for (Filter filter : _FILTER_INSTANCE_CACHE.values()) {
            filter.destroy();
        }
    }

    /**
     * 根据 loadOnStartup 排序
     */
    private static List<ServletRegistration> sortByLoadOnStartup(Collection<? extends ServletRegistration> servletRegistrations) {
        List<ServletRegistration> list = new ArrayList<ServletRegistration>(servletRegistrations);

        Comparator<ServletRegistration> comparator = new Comparator<ServletRegistration>() {
            public int compare(ServletRegistration o1, ServletRegistration o2) {
                Integer a1 = ((BudoServletRegistration) o1).getLoadOnStartup();
                Integer a2 = ((BudoServletRegistration) o2).getLoadOnStartup();

                return a1.compareTo(a2);
            }
        };

        Collections.sort(list, comparator);
        return list;
    }

    private synchronized static void initServletInstance(ServletContext servletContext) {
        try {
            Collection<? extends ServletRegistration> servletRegistrations = servletContext.getServletRegistrations().values();
            List<ServletRegistration> sortedServletRegistrations = sortByLoadOnStartup(servletRegistrations);

            for (ServletRegistration servletRegistration : sortedServletRegistrations) {
                String servletType = servletRegistration.getClassName();
                if (_SERVLET_INSTANCE_CACHE.containsKey(servletType)) { // 防止重复
                    log.warn("#115 initServletInstance, already inited, continue, servletType=" + servletType);
                    continue;
                }

                Servlet servlet = (Servlet) Class.forName(servletType).newInstance();
                BudoServletConfig servletConfig = new BudoServletConfig(servletRegistration);
                servlet.init(servletConfig);

                _SERVLET_INSTANCE_CACHE.put(servletType, servlet);
                log.info("#95 initServlet, servletRegistration=" + servletRegistration);
            }
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    private synchronized static void initFilterInstance(ServletContext servletContext) {
        try {
            Collection<? extends FilterRegistration> filterRegistrations = servletContext.getFilterRegistrations().values();
            for (FilterRegistration filterRegistration : filterRegistrations) {
                String filterType = filterRegistration.getClassName();
                if (_FILTER_INSTANCE_CACHE.containsKey(filterType)) { // 防止重复
                    log.warn("#115 initFilterInstance, already inited, continue, filterType=" + filterType);
                    continue;
                }

                Filter filter = (Filter) Class.forName(filterType).newInstance();
                BudoFilterConfig filterConfig = new BudoFilterConfig(filterRegistration);
                filter.init(filterConfig);

                _FILTER_INSTANCE_CACHE.put(filterType, filter);
                log.info("#109 initFilter, filterRegistration=" + filterRegistration);
            }
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    public static EventListener listenerInstance(Class<? extends EventListener> listenerClass) {
        try {
            return listenerClass.newInstance();
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    public static Class<? extends EventListener> listenerType(String className) {
        try {
            return (Class<? extends EventListener>) Class.forName(className);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }
}